<?php
/**
 * Zhub - A PHP Framework For API Developer
 *
 * This file is part of the Zhub package.
 *
 * (c) YiLong Fan <fantiq@163.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * @author fanyilong <fantiq@163.com>
 * @copyright 2017 fanyilong. All rights reserved.
 * @package Zhub
 * @date 18-2-27
 */

namespace Zhub\Prototype\Objective\Clazz\Method;


use Zhub\DataStruct\Struct;
use Zhub\Prototype\Objective\Clazz\Clazz;
use Zhub\Prototype\Objective\Clazz\ClazzEnum;
use Zhub\Prototype\Objective\Clazz\ClazzUtil;
use Zhub\Prototype\Objective\Clazz\Exceptions\InvalidKeyWordException;
use Zhub\Prototype\Variable\Type;
use Zhub\Prototype\Variable\TypeEnum;

class MethodStruct extends Struct
{
    const ACCESS = 'access';

    const STATIC = 'static';

    const FINAL = 'final';

    const ABSTRACT = 'abstract';

    const NAME = 'name';

    const RETURN_TYPE = 'returnType';

    const PARAMETERS = 'parameters';

    const CONTENT = 'content';

    public static function initialize(): array
    {
        return [
            self::ACCESS => ClazzEnum::ACCESS_PUBLIC,
            self::STATIC => false,
            self::FINAL => false,
            self::ABSTRACT => false,
            self::NAME => '',
//            self::RETURN_TYPE => function () { return Type::newByTypeId(TypeEnum::VOID);},
            self::RETURN_TYPE => null,
            self::PARAMETERS => function () { return ArgumentMap::class;},
            self::CONTENT => null,
        ];
    }

    public function getAccess():int
    {
        return $this->get(self::ACCESS);
    }

    public function setAccess(int $access)
    {
        $this->set(self::ACCESS, $access);
    }

    public function getStatic():bool
    {
        return $this->get(self::STATIC);
    }

    public function setStatic(bool $static)
    {
        $this->set(self::STATIC, $static);
    }

    public function getFinal():bool
    {
        return $this->get(self::FINAL);
    }

    public function setFinal(bool $final)
    {
        if ($final && $this->getAbstract()) {
            throw new InvalidKeyWordException(
                ClazzEnum::name(ClazzEnum::ABSTRACT),
                ClazzEnum::name(ClazzEnum::FINAL)
            );
        }
        $this->set(self::FINAL, $final);
    }

    public function getAbstract():bool
    {
        return $this->get(self::ABSTRACT);
    }

    public function setAbstract(bool $abstract)
    {
        if ($abstract && $this->getFinal()) {
            throw new InvalidKeyWordException(
                ClazzEnum::name(ClazzEnum::FINAL),
                ClazzEnum::name(ClazzEnum::ABSTRACT)
            );
        }
        $this->set(self::ABSTRACT, $abstract);
    }

    public function getName():string
    {
        return $this->get(self::NAME);
    }

    public function setName(string $name)
    {
        ClazzUtil::checkName($name);
        $this->set(self::NAME, $name);
    }

    public function getReturnType():?Type
    {
        return $this->get(self::RETURN_TYPE);
    }

    public function setReturnType(?Type $returnType)
    {
        $this->set(self::RETURN_TYPE, $returnType);
    }

    public function getParameters():ArgumentMap
    {
        return $this->get(self::PARAMETERS);
    }

    public function setParameters(ArgumentMap $parameters)
    {
        $this->set(self::PARAMETERS, $parameters);
    }

    public function getContent():?string
    {
        return $this->get(self::CONTENT);
    }

    public function setContent(string $content)
    {
        if ($this->getAbstract()) {
            $content = null;
        }
        $this->set(self::CONTENT, $content);
    }

    public function toString(Clazz $clazz = null): string
    {
        // abstract / final
        $tpl = '';
        if ($this->getAbstract()) {
            $tpl = ClazzEnum::name(ClazzEnum::ABSTRACT) . ' ';
        }
        elseif ($this->getFinal()) {
            $tpl = ClazzEnum::name(ClazzEnum::FINAL) . ' ';
        }
        // access [public protected private] & static
        $tpl .= ClazzEnum::name($this->getAccess());
        $tpl .= ($this->getStatic() ? ' ' . ClazzEnum::name(ClazzEnum::STATIC) : '') .
            // function define line
            ' function ' . $this->getName() . '(%s)';
        if ($returnType = $this->getReturnType()) {
            if ($returnType->getId() == TypeEnum::CLAZZ) {
                $fullClassName  = $returnType->getValue();
                $clazz->uses->put($fullClassName);
                list($returnTypeName) = ClazzUtil::parseClassNameSpace($fullClassName);
            }
            else {
                $returnTypeName = $returnType->getName();
            }
            $tpl .= ':' . $returnTypeName;
        }

        // function body
        // 如果是抽象方法 则没有方法体
        $tpl .= ($this->getAbstract() ? ';' : PHP_EOL . '{' . PHP_EOL . "\t%s" . PHP_EOL . '}');

        // function parameters
        $parameters = $this->getParameters()->each(function (ArgumentStruct $argument) use ($clazz) {
            return $argument->toString($clazz);
        });

        return sprintf($tpl, implode(', ', $parameters), $this->getContent());
    }

}
